﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace RVNetworkLib
{
    public class Server
    {
        private UdpClient _msgListener;
        private Thread _recvThread;

        public RoomStatus RoomStatus { get; set; }

        public Server(RoomStatus rs)
        {
            RoomStatus = rs;

            // Connection to recv/send message from/to clients
            _msgListener = new UdpClient();
            _msgListener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            _msgListener.ExclusiveAddressUse = false;
            _msgListener.Client.Bind(new IPEndPoint(IPAddress.Any, NetworkEnv.SERVER_PORT));

            _recvThread = new Thread(() => receiveMsg());
            _recvThread.Start();
        }

        public void Close()
        {
            _msgListener.Close();
            _msgListener = null;

            _recvThread.Join();
            _recvThread = null;
        }

        private void receiveMsg()
        {
            if (_msgListener == null) return;

            IPEndPoint iep = new IPEndPoint(IPAddress.Any, NetworkEnv.SERVER_PORT);

            try
            {
                while (true)
                {
                    byte[] data = _msgListener.Receive(ref iep);


                    System.Diagnostics.Trace.WriteLine(String.Format("Receive packet from : {0}:{1}",
                        iep.Address.ToString(), iep.Port));

                    Packet packet = Packet.Deserialize(data);

                    if (packet.Type == PacketType.LQUERY_ROOM_LIST)
                    {
                        HandleLQueryRoomList(packet, iep);
                    }
                    else if (packet.Type == PacketType.LJOIN_ROOM)
                    {
                        HandleJoinRoom(packet, iep);
                    }
                    else if (packet.Type == PacketType.ACK)
                    {
                        System.Diagnostics.Trace.WriteLine("Receive ACK");
                    }
                    else if (packet.Type == PacketType.NAK)
                    {
                        System.Diagnostics.Trace.WriteLine("Receive NAK");
                    }
                    else
                    {
                        System.Diagnostics.Trace.WriteLine("Receive unknown packet from client");
                    }
                }
            }
            catch (System.Net.Sockets.SocketException)
            {
                System.Diagnostics.Trace.WriteLine("Socket closed");
            }
        }

        private void HandleLQueryRoomList(Packet packet, IPEndPoint ep)
        {
            System.Diagnostics.Trace.WriteLine("Receive Lan Query Room List");

            Packet resp = new Packet();
            resp.MagicNumber = 0;
            resp.CheckSum = 0;
            resp.Type = PacketType.LQUERY_ROOM_LIST_R;
            resp.UserID = 0;

            LanRoomInfo lri = new LanRoomInfo();
            lri.IP = Utility.MyIP();
            lri.Port = NetworkEnv.SERVER_PORT;
            lri.Detail = new BriefRoomStatus();
            lri.Detail.RoomID = RoomStatus.RoomID;
            lri.Detail.RoomName = RoomStatus.RoomName;
            lri.Detail.MapName = RoomStatus.MapName;
            lri.Detail.Mode = RoomStatus.Mode;
            lri.Detail.MaxUser = RoomStatus.MaxUser;
            lri.Detail.NumUser = RoomStatus.NumUser;
            lri.Detail.State = RoomStatus.State;

            resp.Payload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(lri));
            resp.DataLength = (uint) resp.Payload.Length;

            byte[] rawData = resp.Serialize();

            IPEndPoint iep = new IPEndPoint(ep.Address, NetworkEnv.CLIENT_PORT);
            _msgListener.Send(rawData, rawData.Length, iep);
        }

        private void HandleJoinRoom(Packet packet, IPEndPoint ep)
        {
            System.Diagnostics.Trace.WriteLine("Receive Join Room");

            // Reply Ack first
            ReplyAck(packet, ep);

            // Check Room full?
            // <TODO> Check duplicate user case!
            if (RoomStatus.NumUser < RoomStatus.MaxUser)
            {
                // Get UserName
                string strPayload = Encoding.UTF8.GetString(packet.Payload);
                JObject obj = JObject.Parse(strPayload);

                ActiveClientStatus acs = new ActiveClientStatus();
                acs.IP = ep.Address.ToString();
                acs.UserID = 0;
                acs.UserName = (string)obj["UserName"];
                acs.RoomID = 0;
                acs.State = ClientStatus.NotReady;

                RoomStatus.Users.Add(acs);
                RoomStatus.NumUser++;

                SendAccept(packet, ep);
                SendUpdateRoomStatus(ep);
            }
            else
            {
                SendReject(packet, ep, RejectCause.RoomIsFull);
            }
        }

        private void ReplyAck(Packet packet, IPEndPoint ep)
        {
            Packet ack = new Packet();
            ack.MagicNumber = 0;
            ack.CheckSum = 0;
            ack.Type = PacketType.ACK;
            ack.UserID = packet.UserID;
            ack.DataLength = 0;
            ack.Payload = null;

            byte[] rawData = ack.Serialize();
            System.Diagnostics.Trace.WriteLine(String.Format("Reply ack to : {0}:{1}",
                ep.Address.ToString(), ep.Port));
            _msgListener.Send(rawData, rawData.Length, ep);
        }

        private void ReplyNak(Packet packet, IPEndPoint ep)
        {
            Packet nak = new Packet();
            nak.MagicNumber = 0;
            nak.CheckSum = 0;
            nak.Type = PacketType.NAK;
            nak.UserID = packet.UserID;
            nak.DataLength = 0;
            nak.Payload = null;

            byte[] rawData = nak.Serialize();
            _msgListener.Send(rawData, rawData.Length, ep);
        }

        private void SendAccept(Packet packet, IPEndPoint ep)
        {
            Packet accept = new Packet();
            accept.MagicNumber = 0;
            accept.CheckSum = 0;
            accept.Type = PacketType.ACCEPT;
            accept.UserID = packet.UserID;
            accept.DataLength = 0;
            accept.Payload = null;

            byte[] rawData = accept.Serialize();
            _msgListener.Send(rawData, rawData.Length, ep);
        }

        private void SendReject(Packet packet, IPEndPoint ep, RejectCause cause)
        {
            Packet reject = new Packet();
            reject.MagicNumber = 0;
            reject.CheckSum = 0;
            reject.Type = PacketType.REJECT;
            reject.UserID = packet.UserID;

            var jsonString = new
            {
                ErrorCode = cause
            };

            reject.Payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(jsonString));
            reject.DataLength = (uint)reject.Payload.Length;

            byte[] rawData = reject.Serialize();
            _msgListener.Send(rawData, rawData.Length, ep);
        }

        private void SendUpdateRoomStatus(IPEndPoint ep)
        {
            Packet rs = new Packet();
            rs.MagicNumber = 0;
            rs.CheckSum = 0;
            rs.Type = PacketType.UPDATE_ROOM_STATUS;
            rs.UserID = 0;
            rs.Payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(RoomStatus));
            rs.DataLength = (uint)rs.Payload.Length;

            byte[] rawData = rs.Serialize();
            _msgListener.Send(rawData, rawData.Length, ep);
        }
    }
}
